package com.je.gateway.router.dispatcher;

import com.google.common.base.Strings;
import com.je.gateway.router.GlobalRouterParams;
import com.je.gateway.router.model.headerRouter.HeaderUrl;
import com.netflix.config.DynamicPropertyFactory;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import org.apache.servicecomb.edge.core.EdgeInvocation;
import org.apache.servicecomb.edge.core.Utils;
import org.apache.servicecomb.transport.rest.vertx.RestBodyHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

/**
 * 请求头路由器
 */
public class UrlHeaderEdgeDispatcher extends GatewayAbstractRestDispatcher {

    private static final Logger logger = LoggerFactory.getLogger(UrlHeaderEdgeDispatcher.class);

    private static final String KEY_ENABLED = "servicecomb.http.dispatcher.edge.headerRouter.enabled";

    public static final String CONFIGURATION_ITEM = "HeaderRoutedConfigurationItem";

    public UrlHeaderEdgeDispatcher() {
        if (this.enabled()) {
            loadConfigurations();
        }
    }

    @Override
    public int getOrder() {
        return 4;
    }

    @Override
    public void init(Router router) {
        String pattern = GlobalRouterParams.GLOBAL_HEADER_ROUTER.getPath();
        router.routeWithRegex(pattern).failureHandler(this::onFailure)
                .handler(this::preCheck)
                .handler(createBodyHandler())
                .handler(this::onRequest);
    }

    protected void preCheck(RoutingContext context) {
        List<HeaderUrl> headerUrls = findConfigurationItems(context.request().path());
        if (headerUrls == null || headerUrls.isEmpty()) {
            context.put(RestBodyHandler.BYPASS_BODY_HANDLER, Boolean.TRUE);
            context.next();
            return;
        }

        HeaderUrl headerUrl = null;
        for (HeaderUrl eachHeaderUrl : headerUrls) {
            if (checkHeaderUrl(context, eachHeaderUrl)) {
                headerUrl = eachHeaderUrl;
                break;
            }
        }

        if (headerUrl == null) {
            context.put(RestBodyHandler.BYPASS_BODY_HANDLER, Boolean.TRUE);
            context.next();
            return;
        }

        context.put(CONFIGURATION_ITEM, headerUrl);
        context.next();
    }

    private boolean checkHeaderUrl(RoutingContext context, HeaderUrl headerUrl) {
        boolean pass = false;
        List<String> headerValues = headerUrl.getHeaderValues();
        for (String eachHeaderValue : headerValues) {
            if (eachHeaderValue.equals(context.request().getHeader(headerUrl.getHeaderKey()))) {
                pass = true;
                headerUrl.setRouterValue(eachHeaderValue);
                break;
            }
        }
        return pass;
    }

    protected void onRequest(RoutingContext context) {
        HeaderUrl headerUrl = context.get(CONFIGURATION_ITEM);
        Boolean bypass = context.get(RestBodyHandler.BYPASS_BODY_HANDLER);
        if (Boolean.TRUE.equals(bypass)) {
            // clear flag
            context.put(RestBodyHandler.BYPASS_BODY_HANDLER, Boolean.FALSE);
            context.next();
            return;
        }
        if (isGateWayPath(context, context.request().path())) {
            context.next();
            return;
        }
        if (headerUrl == null) {
            context.response().setStatusCode(404).end("error matching param url");
            return;
        }

        String path;
        if (!Strings.isNullOrEmpty(headerUrl.getRealPath())) {
            path = headerUrl.getRealPath();
        } else {
            path = Utils.findActualPath(context.request().path(), headerUrl.getPrefixSegmentCount());
        }

        EdgeInvocation edgeInvocation = createEdgeInvocation(context);
        if (headerUrl.getVersionRule() != null) {
            edgeInvocation.setVersionRule(headerUrl.getVersionRule());
        }

        edgeInvocation.init(headerUrl.getMicroServiceName(), context, path, httpServerFilters);
        logger.info("Matching with header dispatcher,the path {} pattern with {} is router to {} begin,the real path is {}! headeKey is {} headeValue is {}", context.request().path(), headerUrl.getPath(), headerUrl.getMicroServiceName(), path, headerUrl.getHeaderKey(), headerUrl.getRouterValue());
        edgeInvocation.edgeInvoke();
        logger.info("Router url {} to {} complete", context.request().path(), headerUrl.getMicroServiceName());
    }

    private List<HeaderUrl> findConfigurationItems(String path) {
        List<HeaderUrl> headerUrls = new ArrayList<>();
        for (HeaderUrl item : GlobalRouterParams.GLOBAL_HEADER_ROUTER.getHeaderUrls()) {
            if (item.getPattern().matcher(path).matches()) {
                headerUrls.add(item);
            }
        }
        return headerUrls;
    }

    private void loadConfigurations() {
        GlobalRouterParams.loadConfigValues();
        GlobalRouterParams.loadUrlHeaderValues();
    }

    @Override
    public boolean enabled() {
        return DynamicPropertyFactory.getInstance().getBooleanProperty(KEY_ENABLED, false).get();
    }

}
